﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Diagnostics;

using SharedGenomics.Core;
using SharedGenomics.Core.Collections;
using SharedGenomics.Annotations;
using SharedGenomics.Workbench.Services;

namespace SharedGenomics.Workbench.Annotations.Requests
{
    public class TavernaAnnotationRequest : IAnnotationRequestStage
    {
        private readonly ITavernaClient _tavernaClient;
        private readonly IAnnotationRepositoryMediator _annotations;
        private readonly IDictionary<int, IList<AnnotationRequestState>> _geneRequestStates;
        private readonly object _geneRequestsLock = new object();
        private readonly SignalledQueue<AnnotationRequestState> _outputRequests;
        private readonly TimedBuffer<int> _geneIdBuffer;

        public TavernaAnnotationRequest(ITavernaClient tavernaClient, IAnnotationRepositoryMediator annotations)
        {
            this._tavernaClient = tavernaClient;
            this._annotations = annotations;

            this._geneRequestStates = new Dictionary<int, IList<AnnotationRequestState>>();
            this._outputRequests = new SignalledQueue<AnnotationRequestState>(rs => rs.TraversalStrategy.MoveNext(rs.Context), 3);
            this._geneIdBuffer = new TimedBuffer<int>(new TimeSpan(0, 0, 5), this.ExecuteRequests);
        }

        public void Process(AnnotationRequestContext context, IAnnotationRequestPipelineTraversalStrategy traversalStrategy)
        {
            bool requestQueued = false;
            string markerName = context.AnnotationTarget as string;
            if (markerName != null)
            {
                int? entrezGeneId = (int?)this._annotations.GetAnnotationForTarget(AnnotationKey.EntrezGeneId, markerName);
                if (entrezGeneId.HasValue)
                {
                    lock (this._geneRequestsLock)
                    {
                        var geneIdRequests = this._geneRequestStates.GetValueForSafe(entrezGeneId.Value, () => new List<AnnotationRequestState>());
                        geneIdRequests.Add(new AnnotationRequestState(context, traversalStrategy));
                    }

                    this._geneIdBuffer.Add(entrezGeneId.Value);
                    requestQueued = true;
                }
                else
                {
                    //fail all requests
                    new[] { AnnotationKey.Gene, AnnotationKey.KeggPathway, AnnotationKey.Publications }.ForEach(key => context.FailRequest(key));
                }
            }

            if (!requestQueued)
            {
                traversalStrategy.MoveNext(context);
            }
        }

        private void ExecuteRequests()
        {
            //execute some outstanding taverna request and get the completed annotation data
            var completedRequests = this._tavernaClient.ExecuteRequest(i => this._geneIdBuffer.UnBuffer(i));
            
            //for each completed annotation:
            //find the list of requests for the given entrez gene id
            //update the annotation
            //insert the request into the outgoing request queue
            IList<AnnotationRequestState> geneRequests;
            foreach (var requestData in completedRequests)
            {
                lock(this._geneRequestsLock)
                {
                    if (this._geneRequestStates.TryGetValue(requestData.EntrezGeneId, out geneRequests))
                    {
                        foreach (var request in geneRequests)
                        {
                            //update the annotation on the current request context
                            var context = request.Context;
                            AnnotationValue entrezValue = new AnnotationValue(AnnotationKey.EntrezGeneId, requestData.EntrezGeneId);

                            var annotations = (new TavernaAnnotationExtractor()).GetAnnotations(requestData);
                            annotations.ForEach(a => context.AddAnnotation(a, entrezValue));

                            //insert into the output request queue
                            this._outputRequests.Enqueue(request);
                        }

                        //remove the list of requests
                        this._geneRequestStates.Remove(requestData.EntrezGeneId);
                    }
                }
            }
        }
    }
}
